package com.yunduansing.demo.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.yunduansing.demo.exception.MyException;
import com.yunduansing.demo.model.common.CacheKeyPrefix;
import com.yunduansing.demo.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
public class AuthUserService implements UserDetailsService {
    @Value("${login.user.expires}")
    private long loginUserExpires;
    @Value("${role.prefix}")
    private String ROLE_PREFIX;

    @Autowired
    private AccountService accountService;
    @Autowired
    private RedisTemplate redisTemplate;
    private PasswordEncoder passwordEncoder;
    private final String CACHE_KEY_PREFIX="token:";
    private final String CACHE_KEY_PREFIX_REFRESH="token:refresh:";

    public AuthUserService() {
        this.passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();  //默认使用 bcrypt， strength=10
    }

    public UserDetails getUserLoginInfo(String username) throws MyException {
        /**
         * @todo 从数据库或者缓存中取出jwt token生成时用的salt
         * salt = redisTemplate.opsForValue().get("token:"+username);
         */
        var salt = redisTemplate.opsForValue().get(CACHE_KEY_PREFIX+username);
        if(salt==null) throw new MyException("获取token key失败。");
        UserDetails user = loadUserByUsername(username);
        //将salt放到password字段返回
        return User.builder().username(user.getUsername()).password(salt.toString()).authorities(user.getAuthorities()).build();
    }

    public String saveUserLoginInfo(UserDetails user) {
        String salt = BCrypt.gensalt(); 
        /**
         * @todo 将salt保存到数据库或者缓存中
         * redisTemplate.opsForValue().set("token:"+username, salt, 3600, TimeUnit.SECONDS);
         */
        redisTemplate.opsForValue().set(CACHE_KEY_PREFIX+user.getUsername(),salt,loginUserExpires, TimeUnit.MILLISECONDS);
        Algorithm algorithm = Algorithm.HMAC256(salt);
        Date date = new Date(System.currentTimeMillis()+loginUserExpires);  //设置过期时间
        var token = JWT.create()
                .withSubject(user.getUsername())
                .withExpiresAt(date)
                .withIssuedAt(new Date())
                .sign(algorithm);
        redisTemplate.opsForValue().set(CacheKeyPrefix.LOGIN_USER_TOKEN+user.getUsername(),token,loginUserExpires, TimeUnit.MILLISECONDS);
        generateRefreshToken(user.getUsername());
        return token;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        var loginUser=accountService.login(username);
        if(loginUser==null) throw new UsernameNotFoundException("User "+username+" not found.");

        List<SimpleGrantedAuthority> simpleGrantedAuthorities=new ArrayList<>();
        for(var item : loginUser.getRoles()){
            simpleGrantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX+item));
        }
        return new User(loginUser.getUsername(),loginUser.getPassword(),simpleGrantedAuthorities);
    }

    public void createUser(String username, String password) {
        String encryptPwd = passwordEncoder.encode(password);
        /**
         * @todo 保存用户名和加密后密码到数据库
         */
    }

    /**
     * 密码加密
     * @param password 密码
     * @return 加密后的密码字符串
     */
    public String encryptPassword(String password){
        return passwordEncoder.encode(password);
    }
    /**
     * 验证密码是否匹配
     * @param originPwd 明文密码
     * @param encryptPwd  加密密码
     * @return
     */
    public boolean validatePassword(String originPwd,String encryptPwd){
        return passwordEncoder.matches(encryptPassword(originPwd), encryptPwd);
    }

    public void deleteUserLoginInfo(String username) {
        /**
         * @todo 清除数据库或者缓存中登录salt
         */
        redisTemplate.delete(CACHE_KEY_PREFIX+username);//移除salt
        redisTemplate.delete(CacheKeyPrefix.LOGIN_USER_TOKEN+username);//移除token
        redisTemplate.delete(CACHE_KEY_PREFIX_REFRESH+username);
        redisTemplate.delete(CacheKeyPrefix.LOGIN_USER_TOKEN+"refresh:"+username);
    }

    private String generateRefreshToken(String username){
        String salt = BCrypt.gensalt(); 
        /**
         * @todo 将salt保存到数据库或者缓存中
         * redisTemplate.opsForValue().set("token:"+username, salt, 3600, TimeUnit.SECONDS);
         */
        redisTemplate.opsForValue().set(CACHE_KEY_PREFIX_REFRESH+username,salt,loginUserExpires, TimeUnit.MILLISECONDS);
        Algorithm algorithm = Algorithm.HMAC256(salt);
        Date date = new Date(System.currentTimeMillis()+loginUserExpires);  //设置过期时间
        var token = JWT.create()
                .withSubject(username)
                .withExpiresAt(date)
                .withIssuedAt(new Date())
                .sign(algorithm);
        redisTemplate.opsForValue().set(CacheKeyPrefix.LOGIN_USER_TOKEN+"refresh:"+username,token,loginUserExpires, TimeUnit.MILLISECONDS);
        return token;
    }
}
